using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Security.Principal;
using System.Text;
using ActiveDirectoryUtilities.CommonLibrary;
using ActiveDirectoryUtilities.DirectoryServiceAccess;
using ActiveDirectoryUtilities.CommonLibrary.Unsafe;
using System.IO;

namespace ActiveDirectoryUtilities.CheckDsAcls
{
	class Program
	{
		private const string CMD_LINE_SWITCH_EXPLICIT = "ExplicitOnly";
		private const string CMD_LINE_SWITCH_SHOW_CHILDREN = "ShowChildren";
		private const string CMD_LINE_SWITCH_SEARCH_FOR_ACCOUNT = "SearchForAcct";
		private const string CMD_LINE_SWITCH_TARGET = "Target";
		private const string CMD_LINE_SWITCH_SHOW_GUID_TRANSLATION = "ShowGuidConversionTables";
		private const string CMD_LINE_SWITCH_SHOW_SACLS = "ShowSacls";
		private const string CMD_LINE_SWITCH_NO_SHOW_DACLS = "NoDacls";
		private const string CMD_LINE_SWITCH_LDAP_SEARCH_FILTER = "SearchFilter";
		private const string CMD_LINE_SWITCH_SHOW_RAW_SID = "ShowSID";
		private const string CMD_LINE_SWITCH_COMPARE_TO_SCHEMA_DEFAULTS = "CompareSchema";
		private const string CMD_LINE_SWITCH_SHOW_ONLY_DELTAS = "ShowOnlyDeltas";  //Must be used in conjunction with CompareSchema otherwise there is nothing to do.
		private const string CMD_LINE_SWITCH_SPLIT_DN_TO_RDNS = "SplitDN";
		private const string CMD_LINE_SWITCH_OUTPUT_TO_FILE = "OutputToFile";
		private const string CMD_LINE_SWITCH_FIX_MISSING_ACES = "FixMissingAces";	//Not in help menu and not implemented
		private const string CMD_LINE_SWITCH_SERVERNAME = "ServerName";
		private const string CMD_LINE_SWITCH_PORT = "Port";
		//private const string CMD_LINE_SWITCH_ONLY_SHOW_SCHEMA_MISSING_ACES = "ShowMissingAces";
		//private const string CMD_LINE_SWITCH_ONLY_SHOW_SCHEMA_ADDITIONAL_ACES = "ShowExtraAces";
		//private const string CMD_LINE_SWITCH_REMOVE_ADDITIONAL_ACES = "RemoveExtraAces";

		static private bool _showOnlyExplicit
			, _showChildren
			, _showGuidTranslations
			, _showSacls
			, _noDacls
			, _compareToDefaultSecurityDescriptor
			, _ensureSchemaDefaultAcesAreInAcls
			, _showOnlyDeltas
			, _splitDNToCSV
			, _showRawIdentityReferenceSid;
		static private IdentityReference _targetAccount;
		// static private string _targetDn = string.Empty;
		static private DNCollection _targetDNs = new DNCollection();
		static private int _largestRDNCount = 0, _port = 3268;
		static private string _targetDomain = string.Empty
								, _targetServer = string.Empty;
		static private string _ldapSearchFilter = "(objectClass=*)";
		static private string _outputFileName = string.Empty;
		static private StreamWriter _swOut = null;

		private const string OUTPUT_FILE_DELIMITER = ",";
		static private Dictionary<Guid, string> _schemaIDGuidTranslationCache = new Dictionary<Guid, string>();
		static private Dictionary<Guid, string> _controlAccessRightTranslationCache = new Dictionary<Guid, string>();
		static private Dictionary<string, string> _defaultObjectClassSecurityDescriptor = new Dictionary<string, string>();

		static void Main(string[] args)
		{
			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

			outputHeader();

			// we need at least .net 2.0 SP1. Higher is OK. 
			if ((Environment.Version.Major < 2) || ((Environment.Version.Major == 2) && (Environment.Version.Revision < 1433)))
			{
				Console.WriteLine(".NET 2.0 SP1 does not appear to be installed (detected " +
					Environment.Version.ToString() + ") .  Please goto http://www.microsoft.com/downloads and install .NET 2.0 SP1");
				return;
			}

			if (parseCommandLine(args))
			{
				FileStream fsOut = null;
				if (!string.IsNullOrEmpty(_outputFileName))
				{
					try
					{
						fsOut = new FileStream(_outputFileName, FileMode.Create, FileAccess.Write, FileShare.Read);
					}
					catch (IOException)
					{
						Console.WriteLine("Unable to create file: {0}", _outputFileName);
					}
					_swOut = new StreamWriter(fsOut);
				}
				outputColumnHeaders();
				foreach (DistinguishedName DN in _targetDNs)
				{
					// not very nice, at the moment. Target domain should be seperate from the DN. 
					_targetDomain = DN.DomainName;
					DN.padLength = _largestRDNCount;
					if (getAcls(DN) == 0)
					{
						Console.WriteLine();
						Console.WriteLine("No results returned for DN: " + DN.ToString());
					}
				}

				if (_swOut != null)
				{
					_swOut.Close();
					_swOut.Dispose();
				}
				if (fsOut != null)
				{
					fsOut.Close();
					fsOut.Dispose();
				}
			}

			#region report guid translations found
			if (_showGuidTranslations)
			{
				Console.WriteLine();
				Console.WriteLine("Exporting Guid translation cache");
				foreach (Guid g in _schemaIDGuidTranslationCache.Keys)
				{
					Console.WriteLine("{0} - {1}", g.ToString(), _schemaIDGuidTranslationCache[g]);
				}

				Console.WriteLine();
				Console.WriteLine("Exporting Control Access Right translation cache");
				foreach (Guid g in _controlAccessRightTranslationCache.Keys)
				{
					Console.WriteLine("{0} - {1}", g.ToString(), _controlAccessRightTranslationCache[g]);
				}
			}
			#endregion
		}

		static private int getAcls(DistinguishedName targetDn)
		{
			//TODO: Target Domain - _targetDomain
			ActiveDirectoryAcls acls = new ActiveDirectoryAcls(targetDn.ToString(), _targetServer, _port);
			acls.LdapSearchFilter = _ldapSearchFilter;
			acls.ShowChildren = _showChildren;
			acls.ShowDacls = !_noDacls;
			acls.ShowSacls = _showSacls;

			int countResults = 0;
			try
			{
				while (acls.MoveNext())
				{
					++countResults;

					ActiveDirectoryAcl acl = (ActiveDirectoryAcl)acls.Current;
					DistinguishedName thisDN = new DistinguishedName(acl.DistinguishedName);
					if (acl.CountAces != 0)
					{
						string securityDescriptorToCompareAgainst = string.Empty;
						if (_compareToDefaultSecurityDescriptor)
						{
							securityDescriptorToCompareAgainst = getSddlForObjectClass(acl.ObjectClass);
							acl.CompareToAcl(securityDescriptorToCompareAgainst);
						}
						foreach (AnalyzableAce ace in acl)
						{
							if (!(_showOnlyExplicit && ace.IsInherited)	//!(If we want to show only explicit options and the acl is inheritable)
								&& ((_targetAccount == null) || (_targetAccount == ace.IdentityReference))
								//If show only deltas is not false or the ace is a delta.
								&& ((!_showOnlyDeltas) || ((ace.IsAceInSecurityDescriptor == AceRelationToReferenceSecurityDescriptor.Missing)
									|| (ace.IsAceInSecurityDescriptor == AceRelationToReferenceSecurityDescriptor.Additional)))
								)
							{
								// dump the ACl datastructure. 
								outputAce(acl, ace);
							}
						}
					}
					else
					{
						// for consistency, prepend the RDN components even in case of null ACL
						string outputString = string.Empty;
						if (_splitDNToCSV)
						{
							outputString = thisDN.PadLeftcsvDN(_largestRDNCount, Program.OUTPUT_FILE_DELIMITER) + Program.OUTPUT_FILE_DELIMITER;
						}
						outputString = outputString + "\"" + ((ActiveDirectoryAcl)acls.Current).DistinguishedName + "\"" + Program.OUTPUT_FILE_DELIMITER + "\"" + "Security Descriptor returned a null value\"";
						outputData(outputString);
					}
				}
			}
			catch (ApplicationException ex)
			{
				Console.WriteLine(ex.Message);
			}
			return countResults;
		}

		static private void outputColumnHeaders()
		{
			string outputString = string.Empty;
			if (_splitDNToCSV)
			{
				for (int i = 1; i <= _largestRDNCount; i++)
				{
					outputString = outputString + "\"RDN-" + i + "\"" + Program.OUTPUT_FILE_DELIMITER;
				}
			}

			outputString = outputString +
				"\"Path\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"SizeOf(Bytes)\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"Owner Account\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"Owner Group\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"AreAccessRulesProtected\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"AreAccessRulesCanonical\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"ACE Type\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"AccessControlType/AuditFlags\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"ActiveDirectoryRights\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"IdentityReference\""
				+ (_showRawIdentityReferenceSid ? Program.OUTPUT_FILE_DELIMITER + "\"IdentityReferenceSID\"" : string.Empty)
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"InheritanceFlags\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"InheritanceType\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"InheritedObjectType\""  //Use Guid and reference schema to find the object type
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"IsInherited\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"ObjectFlags\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"ObjectType\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"PropagationFlags\""
				+ (_compareToDefaultSecurityDescriptor ? Program.OUTPUT_FILE_DELIMITER + "\"RelationToDefaultSecurityDescriptor\"" : string.Empty);

			outputData(outputString);
		}

		static private void outputAce(ActiveDirectoryAcl acl, AnalyzableAce ace)
		{
			string outputString = string.Empty;
			DistinguishedName thisDN = new DistinguishedName(acl.DistinguishedName);

			// prepend RDN components when required. 
			if (_splitDNToCSV)
			{
				outputString = thisDN.PadLeftcsvDN(_largestRDNCount, Program.OUTPUT_FILE_DELIMITER) + Program.OUTPUT_FILE_DELIMITER;
			}

			outputString = outputString + 
				"\"" + acl.DistinguishedName + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + acl.SizeOfSecurityDescriptor.ToString("N0") + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + acl.OwnerAccount.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + acl.OwnerGroup.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + acl.AreAccessRulesProtected.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + acl.AreAccessRulesCanonical.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.AceType + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.AccessControlType.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.ActiveDirectoryRights.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.IdentityReference.ToString() + "\""
				+ (_showRawIdentityReferenceSid ? Program.OUTPUT_FILE_DELIMITER + "\"" + ace.IdentityReference.Translate(typeof(SecurityIdentifier)).ToString() + "\"" : string.Empty)
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.InheritanceFlags.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.InheritanceType.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + translateObjectTypeGuidToFriendlyName(ace.InheritedObjectType) + "\""  //Use Guid and reference schema to find the object type
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.IsInherited.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.ObjectFlags.ToString() + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + translateExtendedRightGuid(ace.ObjectType) + "\""
				+ Program.OUTPUT_FILE_DELIMITER
				+ "\"" + ace.PropagationFlags.ToString() + "\""
				+ (_compareToDefaultSecurityDescriptor
					? Program.OUTPUT_FILE_DELIMITER + "\"" + ace.IsAceInSecurityDescriptor.ToString() + "\""
					: string.Empty);

			outputData(outputString);
		}

		static private void outputData(string lineToWrite)
		{
			if (_swOut == null)
			{
				Console.WriteLine(lineToWrite);
			}
			else
			{
				_swOut.WriteLine(lineToWrite);
			}
		}

		static private string getSddlForObjectClass(string objectClass)
		{
			//Check cache, if not in cache, bind to schema, translate this into an object type, cache, return friendly.
			if ((!string.IsNullOrEmpty(objectClass)) && (!_defaultObjectClassSecurityDescriptor.ContainsKey(objectClass)))
			{
				//Get the data from AD
				RootDse rootDse = new RootDse(_targetDomain);
				WLdapSearchRequest wlsr = new WLdapSearchRequest(
					_targetDomain
					, (int)LDAP_PORTS.LDAP
					, rootDse.SchemaNamingContext
					, "(&(objectCategory=classSchema)(lDAPDisplayName=" + objectClass + "))"
					, System.DirectoryServices.Protocols.SearchScope.Subtree
					);

				wlsr.Attributes.Add("defaultSecurityDescriptor");

				SearchResultEntry sre = wlsr.FindOne();
				if (sre != null)
				{
					if (sre.Attributes.Contains("defaultSecurityDescriptor"))
					{
						_defaultObjectClassSecurityDescriptor.Add(objectClass, sre.Attributes["defaultSecurityDescriptor"][0].ToString());
					}
					else
					{
						_defaultObjectClassSecurityDescriptor.Add(objectClass, null);
					}
				}
				else
				{
					_defaultObjectClassSecurityDescriptor.Add(objectClass, null);
				}
			}
			else if (string.IsNullOrEmpty(objectClass))
			{
				return string.Empty;
			}
			return _defaultObjectClassSecurityDescriptor[objectClass];
		}

		static private string translateExtendedRightGuid(Guid rightsGuid)
		{
			if (!_controlAccessRightTranslationCache.ContainsKey(rightsGuid))
			{
				string extendedRight = GuidFinder.TranslateExtendedRightGuidToFriendlyName(rightsGuid);
				if (!string.IsNullOrEmpty(extendedRight))
				{
					_controlAccessRightTranslationCache.Add(rightsGuid, extendedRight);
				}
				else
				{
					extendedRight = GuidFinder.TranslateObjectTypeSchemaIDGuidToFriendlyName(rightsGuid);
					if (!string.IsNullOrEmpty(extendedRight))
					{
						_controlAccessRightTranslationCache.Add(rightsGuid, extendedRight);
					}
					else
					{
						_controlAccessRightTranslationCache.Add(rightsGuid, rightsGuid.ToString());
					}
				}
			}
			return _controlAccessRightTranslationCache[rightsGuid];
		}

		static private string translateObjectTypeGuidToFriendlyName(Guid classGuid)
		{
			if (!_schemaIDGuidTranslationCache.ContainsKey(classGuid))
			{
				string objectType = GuidFinder.TranslateObjectTypeSchemaIDGuidToFriendlyName(classGuid);
				if (!string.IsNullOrEmpty(objectType))
				{
					_schemaIDGuidTranslationCache.Add(classGuid, objectType);
				}
				else
				{
					_schemaIDGuidTranslationCache.Add(classGuid, classGuid.ToString());
				}
			}
			return _schemaIDGuidTranslationCache[classGuid];
		}

		static private bool parseCommandLine(string[] args)
		{
			string sDN = null;
			bool readFromStdin = Kernel32.stdinRedirected;

			// arguments are required unless reading from stdin; this should be allowed: dsquery ou | checkdsacls
			if (!readFromStdin && (args.Length < 1))
			{
				outputHelp(string.Empty);
				return false;
			}

			foreach (string s in args)
			{
				if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_EXPLICIT.ToUpperInvariant()))
				{
					_showOnlyExplicit = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SHOW_CHILDREN.ToUpperInvariant()))
				{
					_showChildren = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SHOW_GUID_TRANSLATION.ToUpperInvariant()))
				{
					_showGuidTranslations = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SHOW_SACLS.ToUpperInvariant()))
				{
					_showSacls = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_NO_SHOW_DACLS.ToUpperInvariant()))
				{
					_noDacls = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_COMPARE_TO_SCHEMA_DEFAULTS.ToUpperInvariant()))
				{
					_compareToDefaultSecurityDescriptor = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SHOW_ONLY_DELTAS.ToUpperInvariant()))
				{
					_showOnlyDeltas = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_FIX_MISSING_ACES.ToUpperInvariant()))
				{
					_ensureSchemaDefaultAcesAreInAcls = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SHOW_RAW_SID.ToUpperInvariant()))
				{
					_showRawIdentityReferenceSid = true;
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SPLIT_DN_TO_RDNS.ToUpperInvariant()))
				{
					_splitDNToCSV = true;
				}
				else if ((s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SEARCH_FOR_ACCOUNT.ToUpperInvariant())) && (s.Contains(":")))
				{
					//TODO:  Handle invalid accounts.
					// _targetAccount = new NTAccount(s.Split(':')[1].Replace('\"', ' ').Trim());
					_targetAccount = new NTAccount(s.Substring(s.IndexOf(":") + 1).Trim().Trim('"'));
				}
				else if ((s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_OUTPUT_TO_FILE.ToUpperInvariant())) && (s.Contains(":")))
				{
					_outputFileName = s.Substring(s.IndexOf(":") + 1).Trim().Trim('"');
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_SERVERNAME.ToUpperInvariant()) && s.Contains(":"))
				{
					_targetServer = s.Substring(s.IndexOf(":") + 1).Trim().Trim('"');
				}
				else if (s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_PORT.ToUpperInvariant()) && s.Contains(":"))
				{
					_port = Convert.ToInt32(s.Substring(s.IndexOf(":") + 1).Trim().Trim('"'));
				}
				else if ((s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_TARGET.ToUpperInvariant())) && (s.Contains(":")))
				{
					if (readFromStdin)
					{
						Console.WriteLine("Explicit DN ignored when reading from STDIN");
					}
					else
					{
						// WK; this breaks on DN's with a ":" -- such as deleted objects. 
						// sDN = s.Split(':')[1].Trim().Trim('"').ToLowerInvariant();
						sDN = s.Substring(s.IndexOf(":") + 1).Trim().Trim('"');

						// check for empty or clearly illegal DN, prevent failure of IndexOf.
						// note to self: this construction is not ready for ADAM. Should fix that sometime. 
						if (string.IsNullOrEmpty(sDN) || (sDN.ToUpperInvariant().IndexOf("DC=") < 0))
						{
							return outputHelp("Illegal DN: " + sDN);
						}
						DistinguishedName dn = new DistinguishedName(sDN);
						_targetDNs.Add(dn);
						_targetDNs.SetMetrics();
						_largestRDNCount = _targetDNs.LargestNumberRDNs;
						_targetDomain = dn.DomainName;
					}
				}
				else if ((s.ToUpperInvariant().Contains(CMD_LINE_SWITCH_LDAP_SEARCH_FILTER.ToUpperInvariant())) && (s.Contains(":")))
				{
					if (readFromStdin)
					{
						Console.WriteLine("LDAP search filter ignored when reading from STDIN\n");
					}
					else
					{
						// _ldapSearchFilter = s.Split(':')[1].Replace('\"', ' ').Trim();
						_ldapSearchFilter = s.Substring(s.IndexOf(":") + 1).Trim().Trim('"');
						if (string.IsNullOrEmpty(_ldapSearchFilter))
						{
							return outputHelp("No searchfilter specified");
						}
					}
				}
				else if (s.Contains("?"))
				{
					return outputHelp(string.Empty);
				}
				else
				{
					return outputHelp(s);
				}
			}

			// If we have the /SplitDN option, we need to know the largest RDN count of any requested
			// DN in advance. That is easy if the DN is given on the commandline, or using stdin,
			// but if we have /showchildren life is not so easy. In that case we need to do the query
			// once in advance to determine the largest RDN count.
			//
			// The only really sure-fire way of getting /SplitDN right every time would be to cache 
			// all output, and calculate the maximum RDN count from that. One (minor) problem we have right
			// now is with /searchforacct. The calculated RDN count might be too large in this case. 
			// However, storing all output doesn't scale at all, so that is not acceptable. 
			//
			if (readFromStdin)
			{
				_showChildren = false;
				while ((sDN = Console.ReadLine()) != null)
				{
					sDN = sDN.Trim().Trim('"').ToLowerInvariant();
					if (sDN.Length < 2)
					{
						break;  // cannot be valid DN 
					}
					_targetDNs.Add(sDN);
				}
				if (_targetDNs.Count < 1)
				{
					Console.WriteLine("No DN's found reading from standard input; nothing to do.");
					return false;
				}
				_targetDNs.SetMetrics();
				_largestRDNCount = _targetDNs.LargestNumberRDNs;
			}
			else if (_showChildren && _splitDNToCSV)
			{
				if (string.IsNullOrEmpty(sDN))
				{
					Console.WriteLine("No base DN specified; nothing to do.");
					return false;
				}
				_largestRDNCount = FindLargestDNCount(new DistinguishedName(sDN), _ldapSearchFilter);
				if (_largestRDNCount < 1)
				{
					Console.WriteLine("The specified DN and ldap query combination returned no results; nothing to do");
					return false;
				}
			}
			else
			{
				; // all other _splitDNToCSV cases are OK.
			}

			if ((_showOnlyDeltas) && (!_compareToDefaultSecurityDescriptor))
			{
				outputHelp(string.Format("/{0} is only valid when /{1} is also used", CMD_LINE_SWITCH_SHOW_ONLY_DELTAS, CMD_LINE_SWITCH_COMPARE_TO_SCHEMA_DEFAULTS));
			}

			//Since we made it all the way through output all the commandline switches used
			foreach (string arg in args)
			{
				Console.WriteLine("Command Line Switch:  {0}", arg);
			}
			Console.WriteLine();
			return true;
		}

		static private int FindLargestDNCount(DistinguishedName dn, string ldapFilter)
		{
			DNCollection tempDNCollection = new DNCollection();
			WLdapSearchRequest wlsr = new WLdapSearchRequest(dn.DomainName, (int)LDAP_PORTS.LDAP, dn.ToString(), ldapFilter);
			WLdapSearchResultCollection src = null;

			wlsr.Scope = System.DirectoryServices.Protocols.SearchScope.Subtree;
			wlsr.Attributes.Add("distinguishedName");
			wlsr.TypesOnly = true;

			if (dn.ToString().ToLower().Contains("cn=deleted objects,"))
			{
				ShowDeletedControl sdc = new ShowDeletedControl();
				wlsr.Controls.Add(sdc);
			}

			try
			{
				src = wlsr.FindAll();
			}
			catch (LdapException ex)
			{
				if (ex.ErrorCode == 81)
				{
					throw new ApplicationException("Could not find a directory server in domain " + _targetDomain);
				}
				throw ex;
			}
			catch (DirectoryOperationException ex)
			{
				throw new ApplicationException(ex.Message + ":  " + dn.ToString() + "\n\rThe closest match:  " + ex.Response.MatchedDN);
			}

			foreach (SearchResultEntry entry in src)
			{
				tempDNCollection.Add(new DistinguishedName(entry.DistinguishedName.ToString()));
			}
			return tempDNCollection.LargestNumberRDNs;
		}

		static private bool outputHelp(string incorrectSyntax)
		{
			Console.WriteLine();
			//Console.WriteLine("Allows filtering based on Security ID and/or whether the ACL is explicit.");
			Console.WriteLine("Command Line Options:  {0} </{1}:distinguishedName of object> [/{2}] [/{3}] [/{4}:<Account Name>] [/{5}:<LDAP Search Filter>] [/{6}] [/{7}] [/{8} [/{9}]] [/{10}:\"FilePath.txt\"] [/{11}:Servename] [/{12}:389]"
				, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
				, CMD_LINE_SWITCH_TARGET, CMD_LINE_SWITCH_EXPLICIT, CMD_LINE_SWITCH_SHOW_CHILDREN
				, CMD_LINE_SWITCH_SEARCH_FOR_ACCOUNT, CMD_LINE_SWITCH_LDAP_SEARCH_FILTER, CMD_LINE_SWITCH_SPLIT_DN_TO_RDNS
				, CMD_LINE_SWITCH_SHOW_RAW_SID, CMD_LINE_SWITCH_COMPARE_TO_SCHEMA_DEFAULTS, CMD_LINE_SWITCH_SHOW_ONLY_DELTAS
				, CMD_LINE_SWITCH_OUTPUT_TO_FILE, CMD_LINE_SWITCH_SERVERNAME, CMD_LINE_SWITCH_PORT);
			Console.WriteLine();
			Console.WriteLine("/{0}\t\tSpecify the target of the search.  You must specify any DNs that\t\tcontain spaces within quotes.  (Required)"
				, CMD_LINE_SWITCH_TARGET);
			Console.WriteLine("/{0}\tOnly return the ACLs explicitly set on the object."
				, CMD_LINE_SWITCH_EXPLICIT);
			Console.WriteLine("/{0}\tReturn the ACLs on this object and all child objects."
				, CMD_LINE_SWITCH_SHOW_CHILDREN);
			Console.WriteLine("/{0}\tSearch all security descriptors to find what objects a user\n\r\t\tor group has access to."
				, CMD_LINE_SWITCH_SEARCH_FOR_ACCOUNT);
			Console.WriteLine("/{0}\tDisplay SACL (does not display by default)."
				, CMD_LINE_SWITCH_SHOW_SACLS);
			Console.WriteLine("/{0}\tDo not display DACL (useful if only interested in SACL)."
				, CMD_LINE_SWITCH_NO_SHOW_DACLS);
			Console.WriteLine("/{0}\tSupply LDAP search filter to narrow the scope of the search.\n\r\t\tMust use with /{1}"
				, CMD_LINE_SWITCH_LDAP_SEARCH_FILTER
				, CMD_LINE_SWITCH_SHOW_CHILDREN);
			Console.WriteLine("/{0}\tValidate explict ACLs against the defaultSecurityDescriptor\n\r\t\tdefined in the schema for the objectClass."
				, CMD_LINE_SWITCH_COMPARE_TO_SCHEMA_DEFAULTS);
			Console.WriteLine("\t- /{0}\tOnly show deltas of comparison."
				, CMD_LINE_SWITCH_SHOW_ONLY_DELTAS);
			Console.WriteLine("/{0}\tAdd column to display output to show raw SID format of the\n\r\t\tIdentityReference."
				, CMD_LINE_SWITCH_SHOW_RAW_SID);
			Console.WriteLine("/{0}\tDisplay DN split into its RDN's for easier postprocessing."
				, CMD_LINE_SWITCH_SPLIT_DN_TO_RDNS);
			Console.WriteLine("/{0}\tSpecify to write to the named file instead of the console."
				, CMD_LINE_SWITCH_OUTPUT_TO_FILE);
			Console.WriteLine();
			Console.WriteLine("Example syntax:");
			Console.WriteLine("{0} /{1}:\"DC=somedomain,DC=someroot,DC=com\""
				, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name, CMD_LINE_SWITCH_TARGET);
			Console.WriteLine(" - Output all ACLs, explicit and inherited, set on this NC.");
			Console.WriteLine("{0} /{1} /{2} /{3}:\"DC=somedomain,DC=someroot,DC=com\" /{4}:\"DOMAIN\\UserOrGroupName\""
				, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
				, CMD_LINE_SWITCH_EXPLICIT, CMD_LINE_SWITCH_SHOW_CHILDREN
				, CMD_LINE_SWITCH_TARGET, CMD_LINE_SWITCH_SEARCH_FOR_ACCOUNT);
			Console.WriteLine(" - Search all the objects in the current domain to find permissions granted to \n\r   security principal.");
			Console.WriteLine("{0} /{1}:\"DC=somedomain,DC=someroot,DC=com\" /{2}:\"(&(objectCategory=person)(objectClass=user))\""
				, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
				, CMD_LINE_SWITCH_TARGET
				, CMD_LINE_SWITCH_LDAP_SEARCH_FILTER);
			Console.WriteLine(" - Collect the security descriptor for all user objects in the domain");
			Console.WriteLine("dsquery ou | {0} /{1}"
				, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name
				, CMD_LINE_SWITCH_SPLIT_DN_TO_RDNS);
			Console.WriteLine(" - read DN's from stdin: all OU's in the domain using dsquery. Add RDN components in output.");

			if (!string.IsNullOrEmpty(incorrectSyntax))
			{
				Console.WriteLine();
				Console.WriteLine("Incorrect Syntax:  {0}", incorrectSyntax);
				Console.WriteLine();
			}

			return false;
		}

		private static void outputHeader()
		{
			Console.WriteLine("Report security permissions on AD objects.  From http://www.codeplex.com/ActiveDirectoryUtils");
			Console.WriteLine("Version:  {0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
			Console.WriteLine();
		}

		static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
		{
			Console.Clear();
			outputHeader();
			Console.WriteLine("This program has experienced a critical error.  Please submit the below information to:\n http://www.codeplex.com/ActiveDirectoryUtils.");
			Exception err = (Exception)e.ExceptionObject;
			Console.WriteLine("Type of Exception:\t{0}", err.GetType());
			Console.WriteLine("Error Message:\t{0}", err.Message);
			Console.WriteLine("Stack Trace:\n{0}", err.StackTrace);
		}
	}
}
